#!/usr/bin/python

import xmlrpclib
import subprocess
import socket
import struct
from threading import Thread, Lock
import Queue
import re
import sys

#Central Node acts as the hub for session requests as well as 
#  data collection
#-Accept requests to start broadcast sessions
#-keep updated latency_file
#-generate new graph_file for each session
#-broadcast tabel updates to nodes
#-gather data

def init_session(broadcast_node_id, destination_node_ids):
    
    #create time data structure
    time_data_final[broadcast_node_id] = dict()
    for node in destination_node_ids:
        time_data_final[broadcast_node_id][node] = []
    
    #get node latency data
    
    f = open('./kshort/latency_file', 'r')
    latency_data = f.read()
    f.close()
    
    #get node mapping
    node_mapping = dict()
    nm_f = open('./node_mapping', 'r')
    node_count = 0
    for line in nm_f:
        node_count += 1
        node_num = int(re.search('^(\d+)', line).group(0))
        print node_num
        node_hostname = re.search('(?<=\s)(.+?)\s', line).group(0)
        print node_hostname
        node_ip = re.search('([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', line).group(0)
        print node_ip
        node_mapping[node_num] = (node_hostname, node_ip)
        node_mapping[node_ip] = node_num
    print node_mapping
    nm_f.close()
    
    arc_count = 0
    for line in latency_data.split('\n'):
        arc_count += 1
        #print "Count / line"
        #print str(arc_count) + " / " + line
    
    n = node_count
    m = arc_count - 1

    destinations = [node_mapping[x] for x in destination_node_ids]
    
    file_name = './kshort/graph_file' + str(node_mapping[broadcast_node_id])
    f = open(file_name, 'w')

    f.write("n %d\n"% n)
    f.write("m %d\n"% m)
    f.write("s 1\n")#source is always node 1
    f.write("t")
    for i in destinations:
        f.write(" %d"% i)
    f.write("\n")
    f.write(latency_data)
    f.close
    
    subprocess.Popen(['./GenerateNodeTable.py', file_name, '100']) 
        
    
def process_packet():
    control_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  
    
    #Enter a loop to check shared queue and send any outstnding packets
    while 1:
        
        packet = packet_queue.get(block=True)
        bytes = packet[0]
        addr = packet[1]
        
        control_bit = bytes[0]
        
        if control_bit == '0':#init session
            # what data do we need
            # broadcast node, set of destinations
            
            broadcast_node_id = socket.inet_ntoa(bytes[1:5])
            broad_node_socket = bytes
            destination_node_ids = []
            temp_len = len(bytes)#will this work maybe?????
            i = 5
            while i < temp_len: 
                temp_dest = socket.inet_ntoa(bytes[i:i+4])
                #print "Adding destination: " + temp_dest
                destination_node_ids.append(temp_dest)
                i += 4
            
            init_session(broadcast_node_id, destination_node_ids)
            
            #send confirmation of session initialization
            ###**Do this in Generate Node Table
            #control_sock.sendto('1', addr)
        
        elif control_bit == '1':#data from node for session
            #[control_bit][broadcast node][dest node][(source_time_float, dest_time_float),..]
            broadcast_node_id = socket.inet_ntoa(bytes[1:5])
        
            destination_node_id = socket.inet_ntoa(bytes[5:9])
            
            time_offset = struct.unpack("d",''.join(bytes[9:17]))[0]
            #print "Time Offset: "
            #print time_offset
            #[(source_timestamp1, dest_timestamp1), (source_timestamp2, dest_timestamp2),...]
            i = 17
            data = []
            while i < len(bytes):
                t1_temp = struct.unpack("d",''.join(bytes[i:i+8]))[0]
                t2_temp = struct.unpack("d", ''.join(bytes[i+8:i+16]))[0]
                #print "Adding Time Values: "
                #print t1_temp, t2_temp
                data.append((t1_temp, t2_temp))
                i += 16
            time_data_final[broadcast_node_id][destination_node_id] = (time_offset, data)
            #print "Time Data Final"
            #print time_data_final
        
        elif control_bit == '2':#terminate transmission
            
            broadcast_node_id = socket.inet_ntoa(bytes[1:5])
            broadcast_t_offset = struct.unpack("d", bytes[5:13])[0]
            
            print "Session " + broadcast_node_id + " terminated..."
            print "Processing session data..."
            
            f_out = open('test_out', 'w')
            #process data
            f_out.write(broadcast_node_id + "\n")
            for node, t_data in time_data_final[broadcast_node_id].iteritems():
                #print "Packet Data for node: " + node + ": "
                #print t_data
                dest_t_offset = t_data[0]
                f_out.write(node + "\n")
                for d in t_data[1]:
                    time_correction =  broadcast_t_offset - dest_t_offset
                    raw_t_diff = d[1] - d[0]
                    delta = raw_t_diff + time_correction
                    #print "("+str(d[0])+", "+str(d[1])+") ~ " +  str(delta)
                    f_out.write(str(delta * 1000) + ",")
                f_out.write("\n")
                #calculate useful stuffs
                
                #print
            #delete session data
            time_data_final[broadcast_node_id] = {}
            
   
table = dict()
table_lock = Lock()

#dict to store test data
time_data_final = dict()

#shared queue for broadcasting packets
# each queue entry should be a tuple 
# containing the next hop ip and data
# (next_hop_ip, data)
packet_queue = Queue.Queue(maxsize=0)

#Threads to 
NUM_PROCESSING_THREADS = 10

for i in range(NUM_PROCESSING_THREADS):
     t = Thread(target=process_packet)
     t.setDaemon(True)
     t.start()
    
HOST = ''
PORT = 50006

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

this_node_id = socket.gethostbyname(socket.gethostname())
print 'Central Node ip: ' + this_node_id
print 'Socket Created. Initializing...'
try:
    s.bind((HOST, PORT))
except socket.error, msg:
    print 'Bind failed. Error code: ' + str(msg[0]) + ', Error message : ' + msg[1]
    sys.exit();

print 'Socket bound on port: ' + str(PORT)

#hang out and listen for stuffs
while 1:
    data, addr = s.recvfrom(1024)
    print 'Connected with', addr[0] + ':' + str(addr[1])
    #process data
    
    packet_queue.put((data, addr))